<template>
  <div class="model-item">
    <!--绑定按钮区域-->
    <div class="d-flex align-items-center" v-if="canOutput || canInput">
      <i class="iconfont icon-data-input bind-icon invisible" v-if="!myCanInput"></i>
      <i v-else @mousedown.stop="startDrag(true)" @mouseup.stop="startDrag(false)"
         @click.stop.prevent="showBound($event,'in')" @mousemove="beginDraw($event,'in')"
         @mouseover="highlight('in')" @mouseleave="offlight()"
         :class="{'iconfont icon-data-input bind-icon': true, 'bound': myModel.in}"></i>

      <i class="iconfont icon-data-output bind-icon invisible" v-if="!myCanOutput"></i>
      <i v-else @mousedown.stop="startDrag(true)" @mouseup.stop="startDrag(false)"
         @click.stop.prevent="showBound($event,'out')" @mousemove="beginDraw($event,'out')"
         @mouseover="highlight('out')" @mouseleave="offlight()"
         :class="{'iconfont icon-data-output bind-icon': true, 'bound-out': myModel.out}"></i>
    </div>
    <div class="model-field text-truncate" @click="isOpen = !isOpen" :style="'padding-left: ' + (intent * 16) + 'px'">
      <i v-if="hasSubItem" :class="{'iconfont':true, 'icon-tree-close': !isOpen, 'icon-tree-open': isOpen}"></i>
      <i v-if="!hasSubItem" class="flex-shrink-0" style="width: 16px;height: 24px;">&nbsp;</i>
      <div :class="{'pointer hover-text-primary': true, 'fw-bolder':intent==0}" @click.stop="viewDetail">
        <template v-if="isArrayItem">
          <span class="text-success">ITEM</span>
        </template>
        <template v-else>
          <span class="text-decoration-line-through text-muted" v-if="myModel.deprecated">{{myModel.name}}</span>
          <template v-else>{{myModel.name}}</template>
          <template v-if="myModel.isRoot && myModel.isExpression">()</template>
        </template>
      </div>
      <span class="ps-1 text-truncate fs-7">
        <span :class="'param-' + myModel.type">
          {{myModel.type}}
        </span>
        <!--title-->
        <span class="text-info" v-if="myModel.title">
          &nbsp;{{myModel.title}}
        </span>
        <span class="text-muted" :title="myModel.mock=='1' ? 'Has Mock' : ('Mock: '+ myModel.mock)" v-if="myModel.mock">&nbsp;M</span>
        <template v-if="myModel.defaultValue">
          <span v-if="['object','array','map','any'].indexOf(myModel.type)==-1 && !isExpression">&nbsp;{{myModel.defaultValue}}</span>
          <span v-else @click.stop="openCodeEditor(myModel.defaultValue, 'view')">&nbsp;{{t('common.view')}}</span>
        </template>
      </span>
      <span class="ps-1 fs-7 pointer text-muted" @click="showComment()">
        {{enumValues}}
      </span>
    </div>
    <div class="model-action" v-if="myCanMutation">
      <i class="iconfont icon-import pointer text-muted hover-primary" @click.stop="openCodeEditor('', 'import')" v-if="(myModel.type=='object' || myModel.type=='array')"></i>
      <i v-else style="width: 16px;height: 24px;">&nbsp;</i>
      <i class="iconfont icon-plus pointer text-muted hover-primary" @click.stop="add" v-if="myModel.type=='object'"></i>
      <i v-else style="width: 16px;height: 24px;">&nbsp;</i>
      <ConfirmRemove @remove="remove" v-if="!isArrayItem"></ConfirmRemove>
      <i v-else style="width: 16px;height: 24px;">&nbsp;</i>
      <i class="iconfont icon-edit pointer text-muted hover-primary" @click.stop="edit"></i>
    </div>
  </div>
  <!--弹窗选择绑定类型菜单-->
  <teleport to="#app">
    <div>
      <div class="shadow-sm" ref="outputTypeMenu" v-if="showOutputTypeMenu" :style="outputTypeStyle" @click.stop.prevent>
        <AdvanceSelect :options="outputAndBoundItems(currBindOutUI)" :hide-toggle="true" @change="(option) => changeOutputOrBound(currBindOutUI, option)"></AdvanceSelect>
      </div>
    </div>
  </teleport>
  <!--绑定的元素菜单-->
  <div class="list-group shadow-sm ms-2" ref="boundPop" v-if="showBoundPop"  @click.stop.prevent>
    <div class="list-group-item justify-content-between d-flex align-items-center" @click="closeDropmenu"
       @mouseover="highlight('', item.meta.id)" @mouseleave="offlight()"
       v-for="(item,index) in boundUIItems" :key="index">
      <div class="text-truncate flex-shrink-0">
        <i :class="`iconfont text-primary icon-${item.type.toLowerCase()}`"></i>&nbsp;{{ item.meta.title || item.type }}
      </div>
      <div class="text-muted ms-2 me-2" v-if="!hasOutputAs(item.type) && showBoundType==='in'">{{t('common.asDataInput')}}</div>
      <div class="text-muted ms-2 me-2" v-if="!hasOutputAs(item.type) && showBoundType==='out'">{{t('common.asDataOutput')}}</div>
      <div class="input-group flex-grow-1 flex-nowrap input-group-sm ms-2 me-2" v-if="hasOutputAs(item.type)">
        <div class="input-group-text border-0 fs-7 p-0 bg-white">{{t('api.outputAS')}}&nbsp;</div>
        <AdvanceSelect :options="outputAsItems(item)" @change="(option) => changeOutputAs(item, option.value, showBoundType)" :default-text="myModel.out[item.meta.id] || ''"></AdvanceSelect>
      </div>
      <div class="input-group flex-grow-1 flex-nowrap input-group-sm me-2" v-if="hasBoundAs(item.type)">
        <div class="input-group-text border-0 fs-7 p-0 bg-white">{{t('api.boundAS')}}&nbsp;</div>
        <AdvanceSelect :options="boundAsItems" @change="(option) => changeBoundAs(item, option.value)" :default-text="myModel.bound?.[item.meta.id] || 'none'"></AdvanceSelect>
      </div>
      <i class="iconfont icon-remove text-danger" @click.stop.prevent="removeBind(item, showBoundType)" ></i>
    </div>
  </div>
  <template v-if="myModel.type=='array' && isOpen">
    <Data :model="myModel.item" :from-type="fromType" :from-id="fromId" :open="open" :path="[...path, myModel.name]"
               :index="0" @update="updateItem" :can-mutation="canMutation" :intent="intent+1"
               :is-expression="isExpression"
               :can-input="false" :can-output="canOutput" :is-array-item="true"></Data>
  </template>
  <template v-else-if="['object', 'file', 'blob'].indexOf(myModel.type) !== -1 && isOpen">
    <div class="text-muted text-center" v-if="!myModel.props || myModel.props.length==0">{{t('api.model.noSubField')}}</div>
    <template v-else>
      <Data v-for="(item, index) in myModel.props" :from-type="fromType" :from-id="fromId" :open="open" :path="[...path, myModel.name]"
                 :is-expression="isExpression"
                 @remove="removeItem" @update="updateItem" :can-input="['file', 'blob'].indexOf(myModel.type) !== -1 ? false : canInput" :can-output="canOutput"
                 :can-mutation="canMutation" :key="index" :intent="intent+1" :model="item" :index="index"></Data>
    </template>
  </template>
  <lay-layer v-model="editDlgVisible" resize :title="isAddProps ? t('api.addData') : t('api.editData')" :shade="true" :area="['520px', '500px']" :btn="buttons">
    <AddData v-if="!isExpression" v-model="editModel" :is-array-item="!isAddProps && isArrayItem"/>
    <AddExpression v-else v-model="editModel" :can-add-expression="!isAddProps && !isArrayItem" :is-array-item="isArrayItem"/>
  </lay-layer>
  <CodeEditorDialog :read-only="codeType=='view'" :title="codeType=='view'?t('api.model.defaultValue'):t('api.model.import')"
                    :hide-variable="true" :ignore-code-error="true"
                    :tip="tip" v-model="codeEditorVisible" :code="code" @update="importData"></CodeEditorDialog>
  <DataInfo :data="myModel" v-model="detailDlgVisible"></DataInfo>
</template>

<script lang="ts" setup>
import { computed, nextTick, onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { layer } from '@layui/layer-vue'
import ConfirmRemove from '@/components/common/ConfirmRemove.vue'
import AddData from '@/components/common/AddData.vue'
import canvas from '@/lib/canvas'
import { useStore } from 'vuex'
import ydhl from '@/lib/ydhl'
import $ from 'jquery'
import baseUIDefines from '@/components/ui/define'
import DataInfo from '@/components/common/DataInfo.vue'
import CodeEditorDialog from '@/components/common/CodeEditorDialog.vue'
import AdvanceSelect from '@/components/common/AdvanceSelect.vue'
import AddExpression from '@/components/common/AddExpression.vue'
// 数据模型展示，可绑定ui
const emit = defineEmits(['remove', 'update'])

const { index, path, canInput, canOutput, fromId, fromType, isArrayItem, canMutation, intent, model, open, isExpression } = defineProps({
  model: Object,
  index: Number,
  path: {
    default: () => [],
    type: Array
  },
  canInput: Boolean,
  canOutput: Boolean,
  open: Boolean, // 默认是否打开
  fromId: String, // 该数据来源于哪里
  fromType: String, // 数据来源那个表
  isArrayItem: Boolean, // 数组结点标识,用于标识数组的第一个结点
  isExpression: Boolean, // 当前数据是不是表达式内的数据
  canMutation: Boolean, // 是否能编辑和删除
  intent: Number // 缩进次数
})
const editModel = ref()
const myModel = computed(() => model)
const { t } = useI18n()
const editDlgVisible = ref(false)
const tip = ref('')

const showBoundPop = ref(false)
const showOutputTypeMenu = ref(false)
const detailDlgVisible = ref(false)
const codeEditorVisible = ref(false)
const myCanMutation = computed(() => {
  if (myModel.value.readonly) return false
  return canMutation
})
const boundPop = ref()
const outputTypeMenu = ref()
const drawFromEl = ref()
const currBindOutUI = ref()
const isAddProps = ref(false)
const codeType = ref<string>('view') // view || import
const isOpen = ref(open)
const showBoundType = ref('')
const code = ref('')
let nameChanged: any = {}
const store = useStore()
const XYInIframe = computed(() => store.state.design.mouseXYInIframe)
const mouseupInFrame = computed(() => store.state.design.mouseupInFrame)
const pageScale = computed(() => store.state.design.scale)
const currFunctionId = computed(() => store.state.design.function.id)
const currPage = computed(() => store.state.design.page)
const versionId = computed(() => store.state.design.pageVersionId[currPage.value.meta.id])
const myCanInput = computed(() => {
  // 数组项，复杂数组或者上级指定不能绑定输入的
  const canNot = isArrayItem ||
    myModel.value.type === 'map' || myModel.value.type === 'object' ||
    !canInput ||
    (myModel.value.type === 'array' && ['array', 'map'].indexOf(myModel.value.item.type) !== -1)
  return !canNot
})
const myCanOutput = computed(() => {
  // 上级指定不能绑定输出的
  if (!canOutput) return false
  if (isArrayItem) {
    // 数组子项是数组的能，其他不能
    return myModel.value.type === 'array'
  }
  return canOutput
})

const enumValues = computed(() => {
  if (!myModel.value.enumValue) {
    return ''
  }
  const enumValue: any = []
  for (const key in myModel.value.enumValue) {
    enumValue.push(key)
  }
  return enumValue.join(' | ')
})
const boundUIItems = computed(() => {
  let items: any = []
  if (showBoundType.value === 'in') {
    if (myModel.value.in) items.push(myModel.value.in)
  } else if (showBoundType.value === 'out') {
    items = myModel.value.out ? Object.keys(myModel.value.out) : []
  }
  const rst: any = []
  for (const itemid of items) {
    const { uiConfig } = store.getters.getUIItem(itemid)
    if (uiConfig) rst.push(uiConfig)
  }
  return rst
})
const currBindType = ref('')
const hoverUIItemId = computed({
  get: () => store.state.design?.hoverUIItemId || undefined,
  set: (v) => {
    store.commit('updatePageState', { hoverUIItemId: v })
  }
})
const selectedUIItemId = computed(() => store.state.design?.selectedUIItemId || undefined)
const selectedPageId = computed(() => store.state.design?.page?.meta?.id)
const outputTypeStyle = computed(() => {
  if (!showOutputTypeMenu.value || !mouseupInFrame.value || !outputTypeMenu.value) return ''
  const rect = outputTypeMenu.value.getBoundingClientRect()
  const position = mouseupInFrame.value.split('_')
  const top = Math.max(0, rect.height + parseFloat(position[1]) > document.body.clientHeight ? (document.body.clientHeight - rect.height - 30) : parseFloat(position[1]))
  return `position:fixed; left:${position[0]}px; top:${top}px; z-index:12;background-color: transparent !important;`
})
const hoverUIItem = computed(() => {
  if (!hoverUIItemId.value) return null
  const { uiConfig } = store.getters.getUIItemInPage(hoverUIItemId.value, selectedPageId.value)
  return uiConfig
})
const save = (index) => {
  if (!isArrayItem) {
    if (!editModel.value.name) {
      layer.msg(t('api.pleaseInputName'))
      return
    }
    if (!editModel.value.name.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/)) {
      layer.msg(t('api.inputNameInvalid'))
      return
    }
  }
  editDlgVisible.value = false
  if (isAddProps.value) { // 添加对象节点
    if (!myModel.value.props) myModel.value.props = []
    myModel.value.props.push(editModel.value)
    emit('update', index, JSON.parse(JSON.stringify(myModel.value)), [])
  } else { // 修改
    const data = JSON.parse(JSON.stringify(editModel.value))
    if (index === -1) { // 复制时把绑定关系全部去掉，并重新生成所有的uuid
      rebuildInOutUuid(data)
    }
    emit('update', index, data, nameChanged)
  }
}
watch(() => editModel.value?.name, (v, oldValue) => {
  if (v === myModel.value.name) {
    nameChanged = {}
    return
  }
  nameChanged = { dataId: editModel.value.uuid, new: v, old: oldValue }
})
watch(() => open, (n) => {
  isOpen.value = n
})
const rebuildInOutUuid = (data: any) => {
  if (!data) return
  data.uuid = ydhl.uuid()

  if (data.in) data.in = null
  if (data.out) data.out = null
  if (data.bound) data.bound = null
  if (data.item) {
    rebuildInOutUuid(data.item)
  }
  if (data.props) {
    for (const prop of data.props) {
      rebuildInOutUuid(prop)
    }
  }
}
const buttons = computed(() => {
  const btns = [
    {
      text: t('common.save'),
      callback: () => save(index)
    },
    {
      text: t('common.cancel'),
      callback: () => {
        editDlgVisible.value = false
      }
    }
  ]
  if (myModel.value.isRoot) {
    btns.splice(1, 0,
      {
        text: t('common.copy'),
        callback: () => {
          save(-1)
        }
      })
  }
  return btns
})

watch(selectedUIItemId, (n, o) => {
  if (n !== o) showOutputTypeMenu.value = false
})

watch(XYInIframe, (v) => {
  if (canvas.getDrawFromId() !== myModel.value.uuid) return
  const ifrmae = document.getElementById('wrapper' + selectedPageId.value)
  if (ifrmae) {
    const { x, y } = ifrmae.getBoundingClientRect()
    // console.log(v.x, v.y, x, y)
    canvas.mouseoverInIframe(v.x * pageScale.value + x, v.y * pageScale.value + y)
  }
})
// 在某个ui上松开后结束画线,并设置绑定关系
watch(mouseupInFrame, (v) => {
  if (!hoverUIItem.value || !canvas.isDrawline() || canvas.getDrawFromId() !== myModel.value.uuid) return
  canvas.stopDrawline()
  currBindOutUI.value = hoverUIItem.value
  if (currBindType.value === 'in') {
    if (!baseUIDefines[hoverUIItem.value.type].isValuable) {
      ydhl.alert(t('variable.bindInputInvalid'))
      return
    }
    if (hoverUIItem.value?.dataIn) {
      ydhl.alert(t('variable.hasBoundInput'))
      return
    }
    saveBound(hoverUIItem.value, currBindType.value, '', hoverUIItem.value.type)
  } else {
    const items = outputAsItems(hoverUIItem.value)
    if (items.length === 0) {
      ydhl.alert(t('variable.cannotBindOutput'))
      return
    }
    showOutputTypeMenu.value = true
  }
})
const boundAsItems = computed(() => {
  return [
    { name: 'None', value: 'none' },
    { name: 'Value', value: 'VALUE', desc: t('variable.boundAsValue') },
    { name: 'Bound', value: 'BOUND', desc: t('variable.boundAsBound') }
  ]
})
const hasSubItem = computed(() => {
  return myModel.value.type === 'array' || myModel.value.type === 'object' || myModel.value.type === 'file' || myModel.value.type === 'blob'
})
const outputAsItems = (uiItem) => {
  if (!uiItem) return []
  const uiType = uiItem.type
  const _: any = []
  const map: any = {
    Iterable: {
      HTML: [],
      TEXT: [],
      VALUELIST: ['map', 'object', 'array'],
      STYLE: ['string', 'number', 'integer', 'map', 'object', 'array'],
      NONE: ['array'],
      CSS: ['string', 'number', 'integer', 'map', 'object', 'array'],
      TITLE: ['string', 'number', 'integer', 'map', 'object', 'array'],
      ALT: ['string', 'number', 'integer', 'map', 'object', 'array'],
      KEYVALUE: ['map', 'object']
    },
    unIterable: {
      HTML: ['string', 'number', 'integer', 'map', 'object', 'array', 'boolean', 'any'],
      TEXT: ['string', 'number', 'integer', 'map', 'object', 'array', 'boolean', 'any'],
      VALUELIST: [],
      VALUE: ['string', 'number', 'integer', 'array'],
      STYLE: ['string', 'number', 'integer', 'map', 'object', 'array'],
      CSS: ['string', 'number', 'integer', 'map', 'object', 'array'],
      TITLE: ['string', 'number', 'integer', 'map', 'object', 'array', 'boolean', 'any'],
      ALT: ['string', 'number', 'integer', 'map', 'object', 'array', 'boolean', 'any'],
      NONE: ['string', 'number', 'integer', 'map', 'object', 'array', 'boolean', 'any'],
      KEYVALUE: ['object', 'map', 'array']
    }
  }
  const dataType = myModel.value.type
  const scaleTypes = ['string', 'number', 'integer']
  const is2DArray = myModel.value.type === 'array' && myModel.value.item.type === 'array'
  const isScale = scaleTypes.indexOf(dataType) !== -1
  const isObject = ['object', 'map'].indexOf(dataType) !== -1
  const isArray = myModel.value.type === 'array'
  const is1DArray = myModel.value.type === 'array' && myModel.value.item.type !== 'array'
  const is1DObjectMapArray = myModel.value.type === 'array' && ['object', 'map'].indexOf(myModel.value.item.type) !== -1
  const is1DObjectArray = myModel.value.type === 'array' && myModel.value.item.type === 'object'
  const is1DScaleArray = myModel.value.type === 'array' && scaleTypes.indexOf(myModel.value.item.type) !== -1
  const is2DScaleArray = is2DArray && scaleTypes.indexOf(myModel.value.item.item.type) !== -1
  const iterate = baseUIDefines[uiType].isIterable ? 'Iterable' : 'unIterable'
  for (const output of baseUIDefines[uiType].outputAs) {
    if (map[iterate][output].indexOf(dataType) === -1) continue
    // css 输出不支持object和map
    if (output === 'CSS' && isObject) continue
    if (iterate === 'Iterable') {
      if (output === 'STYLE' && !isObject && !is1DScaleArray && !isScale) continue
      if (output === 'CSS' && !is1DScaleArray && !isScale) continue
      if (output === 'NONE') {
        if (uiType === 'Table') {
          if (!is1DObjectArray) continue
        } else {
          if (!is2DArray) continue
        }
      }
      if (output === 'VALUELIST' && uiType === 'Collapse' && !is1DObjectMapArray) continue
      if (output === 'VALUELIST' && uiType === 'Carousel' && (isObject || is1DObjectMapArray || (is2DArray && !is2DScaleArray))) continue
      if (output === 'VALUELIST' && uiType === 'Table' && !is2DArray) continue
    } else {
      if (output === 'VALUE' && !isScale && !is1DScaleArray) continue
      if (output === 'STYLE' && !isScale && !isObject && !is1DArray && (is2DArray && !is2DScaleArray)) continue
      if (output === 'CSS' && !isScale && !is1DScaleArray && (is2DArray && !is2DScaleArray)) continue
      if (output === 'KEYVALUE' && !isObject && !is1DObjectMapArray) continue
      if (output === 'NONE' && !isArray) continue
    }
    _.push({ name: output + (uiItem.dataOut?.[output] ? ` (${t('variable.hasBeenBound')})` : ''), value: output, desc: t('variable.output.' + output), disabled: !!uiItem.dataOut?.[output] })
  }
  return _
}
const outputAndBoundItems = (uiItem) => {
  const items: any = outputAsItems(uiItem)
  if (hasBoundAs(uiItem.type)) {
    items.splice(0, 0, { header: t('api.outputAS') })
    items.push({ header: t('api.boundAS') })
    items.push({ name: 'VALUE', value: 'VALUE', type: 'bound', desc: t('variable.boundAsValue') })
    items.push({ name: 'BOUND', value: 'BOUND', type: 'bound', desc: t('variable.boundAsBound') })
  }
  return items
}
const hasOutputAs = (uiType) => {
  if (showBoundPop.value && showBoundType.value !== 'out') return false
  if (baseUIDefines[uiType].outputAs.length === 0) return false
  return true
}
const hasBoundAs = (uiType) => {
  if (!hasOutputAs(uiType)) return false
  if (baseUIDefines[uiType].isForm) return false
  if (baseUIDefines[uiType].isIterable) return false
  return true
}
const changeOutputAs = (item, type, boundType) => {
  showOutputTypeMenu.value = false
  saveBound(item, boundType, type, item.type)
}
const changeBoundAs = (item, boundAs) => {
  showOutputTypeMenu.value = false
  saveBound(item, 'out', myModel.value.out?.[item.meta.id] || 'NONE', item.type, boundAs)
}
const changeOutputOrBound = (uiItem, option) => {
  if (option?.type === 'bound') {
    changeBoundAs(uiItem, option.value)
  } else {
    changeOutputAs(uiItem, option.value, 'out')
  }
}

const startDrag = (bool: boolean) => {
  // console.log('mousedown')
  canvas.setStartDrag(bool)
}
const beginDraw = (event, type) => {
  if (!canvas.isDrag() || canvas.isDrawline()) return
  // console.log(props.model.name, props.model.type, props.model.uuid, type)
  currBindType.value = type
  drawFromEl.value = event.target
  canvas.startDrawline(event.clientX, event.clientY, myModel.value.uuid)
}

const edit = () => {
  isAddProps.value = false
  editModel.value = JSON.parse(JSON.stringify(myModel.value))
  nameChanged = {}
  editDlgVisible.value = true
}
const add = () => {
  isAddProps.value = true
  editModel.value = { type: 'string', uuid: ydhl.uuid(), isExpression: isExpression }
  nameChanged = {}
  editDlgVisible.value = true
}
const showComment = () => {
  layer.confirm(myModel.value.comment, { title: 'YDUIBuilder' })
}

// 删除根级数据
const remove = () => {
  // 先把绑定删除
  const inItems: any = []
  let outItems: any = []
  if (myModel.value.in) inItems.push(myModel.value.in)
  if (myModel.value.out) outItems = Object.keys(myModel.value.out)

  const promises = []
  for (const itemid of inItems) {
    const { uiConfig } = store.getters.getUIItem(itemid)
    promises.push(slinceRemoveBind(uiConfig, 'in'))
  }
  for (const itemid of outItems) {
    const { uiConfig } = store.getters.getUIItem(itemid)
    promises.push(slinceRemoveBind(uiConfig, 'out'))
  }
  Promise.all(promises).then(() => {
    emit('remove', index)
  })
}
// 删除数据中的数据
const removeItem = (index: number) => {
  myModel.value.props.splice(index, 1)
  emit('update', index, myModel.value) // 通知最顶层通过接口更新后端
}
const updateItem = (index: number, item: any, nameChanged: Record<string, string>) => {
  if (myModel.value.type === 'array') {
    myModel.value.item = item
  } else {
    myModel.value.props[index] = item
  }
  emit('update', index, myModel.value, nameChanged) // 通知最顶层通过接口更新后端
}
// 高亮显示绑定的元素
const highlight = (type, uuid = null) => {
  if (uuid) {
    store.commit('updatePageState', { highlightUIItemIds: [uuid] })
    return
  }
  let items: any = []
  if (type === 'in') {
    if (myModel.value.in) items.push(myModel.value.in)
  } else {
    items = myModel.value.out ? Object.keys(myModel.value.out) : []
  }
  store.commit('updatePageState', { highlightUIItemIds: items })
}
const offlight = () => {
  store.commit('updatePageState', { highlightUIItemIds: [] })
}
const showBound = (event: any, type: string) => {
  if (canvas.isDrawline()) return
  showBoundType.value = type
  // 由于阻止了时间冒泡，所以这里通过触发body上的click来通知其他的data.vue中的弹窗关闭
  $('body').trigger('click')
  nextTick(() => {
    ydhl.togglePopper(showBoundPop, event.target, boundPop, 'bottom')
  })
}
const viewDetail = () => {
  detailDlgVisible.value = true
}

const saveBound = (uiItem, type, outputAs, uiType, boundAs = '') => {
  const uiid = uiItem.meta.id
  ydhl.savePage(currFunctionId.value, currPage.value, versionId.value, (rst) => {
    const data: any = { saving: false }
    if (rst?.success) {
      data.saved = 1
      data.pageUuid = currPage.value.meta.id
      data.versionId = rst.data.versionId
    }
    store.commit('updateSavedState', data)
    const output = hasOutputAs(uiType) ? outputAs : ''
    ydhl.post('api/bind/io.json', {
      data_id: myModel.value.uuid,
      page_uuid: selectedPageId.value,
      ui_id: uiid,
      type,
      output_as: output,
      bound_as: boundAs,
      from_uuid: fromId
    }, [], (rst) => {
      if (!rst || !rst.success) {
        ydhl.alert(rst.msg || t('common.operationFail'), t('common.ok'))
        return
      }
      const metaProps: any = {}
      const myPath = [...path]// 断掉引用
      myPath.push(myModel.value.name)

      if (type === 'out') {
        if (!myModel.value.out) myModel.value.out = {}
        if (!myModel.value.bound) myModel.value.bound = {}
        const oldOutput = myModel.value.out[uiid]
        myModel.value.out[uiid] = output
        // 删除原来的输入类型，改成新的输入类型
        const old = uiItem.dataOut ? JSON.parse(JSON.stringify(uiItem.dataOut)) : {}
        delete old[oldOutput]
        old[output] = myPath.join('.')
        metaProps.dataOut = old

        if (boundAs) {
          const oldBound = myModel.value.bound[uiid]
          myModel.value.bound[uiid] = boundAs
          const _old = uiItem.dataBound ? JSON.parse(JSON.stringify(uiItem.dataBound)) : {}
          delete _old[oldBound]
          _old[boundAs] = myPath.join('.')
          metaProps.dataBound = _old
        }
      } else {
        if (myModel.value.in) {
          // 数据只能有一个ui提供输入，删除原来的输入绑定ui
          const { uiConfig } = store.getters.getUIItem(myModel.value.in)
          if (uiConfig) {
            store.commit('updateUIInfo', {
              itemid: myModel.value.in,
              pageId: selectedPageId.value,
              props: {
                dataIn: null
              }
            })
          }
        }

        myModel.value.in = uiid
        metaProps.dataIn = { path: myPath.join('.') }
      }
      emit('update', index, myModel.value) // 通知最顶层通过接口更新后端
      store.commit('updateUIInfo', {
        itemid: uiid,
        pageId: selectedPageId.value,
        props: metaProps
      })
    }
    , 'json')
  })
}
const slinceRemoveBind = (uiItem: any, type: string) => {
  const uiid = uiItem.meta.id
  const outputAs = myModel.value.out?.[uiid]

  return new Promise((resolve) => {
    ydhl.post('api/bind/remove.json', { ui_id: uiid, type, data_id: myModel.value.uuid, page_uuid: selectedPageId.value }, [], (rst) => {
      resolve(true)
      if (!rst.success) {
        return
      }
      const metaProps: any = {}
      if (type === 'out') {
        delete uiItem.dataOut?.[outputAs]
        metaProps.dataOut = uiItem.dataOut ? JSON.parse(JSON.stringify(uiItem.dataOut)) : {}
      } else {
        uiItem.dataIn = null
        metaProps.dataIn = null
      }
      store.commit('updateUIInfo', {
        itemid: uiid,
        pageId: selectedPageId.value,
        props: metaProps
      })
    })
  })
}
const removeBind = (uiItem: any, type: string) => {
  const uiid = uiItem.meta.id
  const outputAs = myModel.value.out?.[uiid]
  ydhl.post('api/bind/remove.json', { ui_id: uiid, type, data_id: myModel.value.uuid, page_uuid: selectedPageId.value }, [], (rst) => {
    if (!rst.success) {
      ydhl.alert(rst.msg || t('common.operationFail'), t('common.ok'))
      return
    }
    if (type === 'in') {
      myModel.value.in = null
    } else {
      delete myModel.value.out?.[uiid]
      if (ydhl.isEmptyObject(myModel.value.out)) myModel.value.out = null
      emit('update', index, myModel.value) // 通知最顶层通过接口更新后端
    }

    const metaProps: any = {}
    if (type === 'out') {
      delete uiItem.dataOut?.[outputAs]
      metaProps.dataOut = uiItem.dataOut ? JSON.parse(JSON.stringify(uiItem.dataOut)) : {}
    } else {
      uiItem.dataIn = null
      metaProps.dataIn = null
    }
    store.commit('updateUIInfo', {
      itemid: uiid,
      pageId: selectedPageId.value,
      props: metaProps
    })
  })
}

const closeDropmenu = () => {
  if (!boundPop.value) return
  boundPop.value.querySelectorAll('.dropdown-toggle').forEach((el) => el.classList.remove('show'))
  boundPop.value.querySelectorAll('.dropdown-menu').forEach((el) => el.classList.remove('show'))
}
const openCodeEditor = (newCode, type) => {
  codeEditorVisible.value = true
  codeType.value = type
  if (type === 'import') {
    const mockJson = ydhl.mockJson(myModel.value)
    code.value = JSON.stringify(mockJson[myModel.value.name])
    tip.value = t('api.model.importTip')
    editModel.value = JSON.parse(JSON.stringify(myModel.value))
  } else {
    tip.value = ''
    code.value = newCode
  }
}
const importData = (newCode) => {
  let json
  try {
    json = JSON.parse(newCode)
  } catch (e: any) {
    ydhl.alert('Parse Error: ' + e.message, t('common.ok'))
    return
  }

  const type = Object.prototype.toString.call(json).slice(8, -1).toLowerCase()
  if (type !== myModel.value.type) {
    ydhl.alert(t('variable.typeMismatch', [myModel.value.type, type]), t('common.ok'))
    return
  }
  codeEditorVisible.value = false
  const oldUuid = editModel.value.uuid
  editModel.value = ydhl.parseJsonData(myModel.value.name, myModel.value.isRoot, json)
  editModel.value.isExpression = isExpression
  editModel.value.uuid = oldUuid
  save(index)
}

onMounted(() => {
  document.body.addEventListener('click', (event: any) => {
    showBoundPop.value = false
    showOutputTypeMenu.value = false
  }, {})
})
</script>
